#pragma once

#include <iostream>
#include <vector>
#include <mutex>
#include<algorithm>
#include <cassert>
#include <fstream>
#include <jsoncpp/json/json.h>
#include "oj_model.hpp"
#include "oj_view.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include "../comm/httplib.h"

namespace ns_control
{
    using namespace ns_model;
    using namespace ns_view;
    using namespace ns_log;
    using namespace ns_util;
    using namespace httplib;

    const std::string machines_conf_path = "./service_machine/service_machine.conf";
    // 主机模块
    class Machine
    {
    public:
        std::string ip; // 编译模块主机ip
        int port;       // 编译模块主机端口号
        size_t load;    // 编译主机负载
        std::mutex *m;  // 锁

        Machine() : ip(""), port(0), load(0), m(nullptr)
        {
        }

        ~Machine()
        {
        }

        size_t Load()
        {
            size_t ret = 0;
            if (m)
                m->lock();
            ret = load;
            if (m)
                m->unlock();
            return ret;
        }

        void IncLoad()
        {
            if (m)
                m->lock();
            ++load;
            if (m)
                m->unlock();
        }

        void DecLoad()
        {
            if (m)
                m->lock();
            --load;
            if (m)
                m->unlock();
        }

        void SetLoad()
        {
            if (m)
                m->lock();
            load = 0;
            if (m)
                m->unlock();
        }
    };

    // 负载均衡模块，对主机进行管理
    class LoadBalance
    {
    private:
        std::vector<Machine> machines;
        std::vector<int> offline_machine;
        std::vector<int> online_machine;

        std::mutex mtx;

    public:
        LoadBalance()
        {
            assert(LoadConf(machines_conf_path));
        }
        ~LoadBalance()
        {
        }

        bool LoadConf(const std::string &machine_path)
        {
            std::ifstream in(machine_path);
            if (!in.is_open())
            {
                LOG(Fatal) << " 打开配置文件: " << machine_path << " 失败" << "\n";
                return false;
            }

            std::string line;
            while (std::getline(in, line))
            {
                std::vector<std::string> vs;
                StringUtil::Splitstr(line, &vs, " ");
                if (vs.size() != 2)
                {
                    LOG(Warning) << " " << line << " 切割失败,请检查配置文件" << "\n";
                    continue;
                }

                Machine m;
                m.ip = vs[0];
                m.port = atoi((vs[1].c_str()));
                m.m = new std::mutex;
                online_machine.push_back(machines.size());
                machines.push_back(m);
            }

            return true;
        }

        bool SmartChoice(int *ret_id, Machine **ret_m)
        {
            mtx.lock();
            if (online_machine.size() == 0)
            {
                mtx.unlock();
                LOG(Fatal) << " 所有编译程序全部掉线，请运维查看! " << "\n";
                return false;
            }

            *ret_id = online_machine[0];
            *ret_m = &machines[online_machine[0]];
            size_t min_load = machines[online_machine[0]].Load();
            for (int i = 0; i < online_machine.size(); i++)
            {
                if (min_load > machines[online_machine[i]].Load())
                {
                    min_load = machines[online_machine[i]].Load();
                    *ret_id = online_machine[i];
                    *ret_m = &machines[online_machine[i]];
                }
            }
            mtx.unlock();
            return true;
        }

        void offline(size_t id)
        {
            mtx.lock();
            auto iter = online_machine.begin();
            while(iter != online_machine.end())
            {
                if(*iter == id)
                {
                    machines[id].SetLoad();
                    offline_machine.push_back(*iter);
                    online_machine.erase(iter);
                    break;
                }
                iter++;
            }
            mtx.unlock();
        }

        void Online()
        {
            mtx.lock();
            online_machine.insert(online_machine.end(),offline_machine.begin(),offline_machine.end());
            offline_machine.erase(offline_machine.begin(),offline_machine.end());
            mtx.unlock();

        }

        //for debug
        void AllMachine()
        {
            mtx.lock();
            //在线主机
            std::cout<<" 在线主机有：";
            for(int i = 0;i<online_machine.size();i++)
            {
                std::cout<<online_machine[i]<<" ";
            }
            std::cout<<std::endl;

            //离线主机
            std::cout<<" 离线主机有：";
            for(int i = 0;i<offline_machine.size();i++)
            {
                std::cout<<offline_machine[i]<<" ";
            }
            std::cout<<std::endl;

            mtx.unlock();
        }
    };

    class control
    {
    private:
        model _model;
        view _view;
        LoadBalance _load_balance;

    public:
        control()
        {
        }
        ~control()
        {
        }

        void OnlineMachines()
        {
            _load_balance.Online();
            LOG(Info)<<"所有主机已经全部上线"<<"\n";
        }

        bool AllQuestionHtml(std::string *html)
        {
            bool ret = true;
            std::vector<struct Question> all;
            if (_model.GetAllQuestion(&all))
            {
                sort(all.begin(),all.end(),[](const Question& q1,const Question& q2){
                    return atoi(q1.number.c_str())<atoi( q2.number.c_str());
                });
                _view.AllExpandHtml(all, html);
            }
            else
            {
                *html = "获取题目列表失败.";
                ret = false;
            }
            return ret;
        }

        bool OneQuestionHtml(const std::string &number, std::string *html)
        {
            bool ret = true;
            struct Question q;
            if (_model.GetOneQuestion(number, &q))
            {
                _view.OneExpandHtml(q, html);
            }
            else
            {
                *html = "获取" + number + "题目失败.";
                ret = false;
            }
            return ret;
        }

        void Judge(const std::string &number, const std::string &in_json, std::string *out_json)
        {
            // 根据题号，获取题目信息
            Question q;
            _model.GetOneQuestion(number, &q);
            // 反序列化获得用户提交的源代码
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);
            std::string all_header = in_value["code"].asString();
            // 将源代码和测试代码拼接
            Json::Value compile_value;
            compile_value["code"] = all_header + q.tail;
            compile_value["input"] = in_value["input"].asString();
            compile_value["cpu_limit"] = q.cpu_limit;
            compile_value["mem_limit"] = q.mem_limit;
            Json::FastWriter writer;
            std::string compile_json = writer.write(compile_value);

            // 负载均衡的选择主机
            while (true)
            {
                int id;
                Machine *ma;
                if (!_load_balance.SmartChoice(&id, &ma))
                {
                    break;
                }
                LOG(Info) << "主机: id:" << id << " 信息：" << ma->ip <<" : "<<ma->port<< " 被选择" << "\n";

                // 发送http请求
                Client cli(ma->ip, ma->port);
                ma->IncLoad();
                LOG(Info) << "主机: " << id << " 负载：" << ma->Load() << "\n";
                if (auto res = cli.Post("/compile_and_server", compile_json, "application/json;charset=utf-8"))
                {
                    // 将结果返回
                    if(res->status == 200)
                    {
                        *out_json = res->body;
                        ma->DecLoad();
                        LOG(Info) <<" 编译运行成功!"<<"\n";
                        break;
                    }
                    else{
                        ma->DecLoad();
                    }
                }
                else
                {
                    LOG(Warning) << "主机: " << id << " : " << ma->ip << " 离线" << "\n";
                    _load_balance.offline(id);
                    _load_balance.AllMachine();//debug function
                }
            }
        }
    };

}